/*
    Copyright (C) 2011 Denis G. <the.same.denis@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

    This program contains code from GNU Wget,
    released under the GNU GPL (http://gnu.org/).
*/

#define _GNU_SOURCE

#include <sys/ioctl.h>
#include <sys/types.h> 
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pwd.h>
#include <string.h>
#include <errno.h>
#include "settings.c"

#define bool char
#define true 1
#define false 0

int pd;
int use_long;
int use_custom_config = 0;

char *config_files[] = { NULL, "~/.config/rmpc4/rmpc4.conf", "/etc/rmpc4.conf" };
char *pid_files[] = { NULL, "/var/run/rmpc4.pid", "~/.config/rmpc4/rmpc4.pid" };

char *procname;
char *procversion = "4.0.8";

struct DATASTRUCT data[] = {
        // Count of members
        {34,"count", NULL},
        // Buttons
        // Number1
        {64, "a", NULL},
        {256, "b", NULL},
        {32, "c", NULL},
        {128, "d", NULL},
        {320, "ab", NULL},
        {96, "ac", NULL},
        {192, "ad", NULL},
        {288, "bc", NULL},
        {384, "bd", NULL},
        {160, "cd", NULL},
        {352, "abc", NULL},
        {448, "abd", NULL},
        {224, "acd", NULL},
        {416, "bcd", NULL},
        {480, "abcd", NULL},
        // Long presses
        // Number16
        {-1, "along", NULL},
        {-1, "blong", NULL},
        {-1, "clong", NULL},
        {-1, "dlong", NULL},
        {-1, "ablong", NULL},
        {-1, "aclong", NULL},
        {-1, "adlong", NULL},
        {-1, "bclong", NULL},
        {-1, "bdlong", NULL},
        {-1, "cdlong", NULL},
        {-1, "abclong", NULL},
        {-1, "abdlong", NULL},
        {-1, "acdlong", NULL},
        {-1, "bcdlong", NULL},
        {-1, "abcdlong", NULL},
        // Other settings
        // Number31
        {-1, "port", "/dev/ttyS0"},
        {-1, "pidfile", NULL},
        {-1, "uselongclick", "yes"}
    };

int
run_command (int cmdcode)
{
    if (data[cmdcode].command == NULL)
        return 0;

    switch (vfork())
    {
        case 0:
            setsid();
            switch(vfork())
            {
                case 0:
                    execlp ("sh", "sh", "-c", data[cmdcode].command, (char *) NULL);
                    _exit(0);
                    break;
                default:
                    _exit(0);
                    break;
            }
            break;
        case -1:
            fprintf(stderr, "%s: Error in vfork()\n", procname);
            break;
        default:
            wait(NULL);
            break;
    }
    return 0;
}

int
get_pin_status()
{
    int flags = 0, state_on = 0;
    ioctl (pd, TIOCMGET, &flags);

    state_on += (flags & TIOCM_CAR);
    state_on += (flags & TIOCM_DSR);
    state_on += (flags & TIOCM_CTS);
    state_on += (flags & TIOCM_RNG);

    return state_on;
}

int
check_buttons()
{
    int i;
    int was_long;
    int buttons_state = 0;
    while(1)
    {
        was_long = 0;
        buttons_state = get_pin_status();
        for (i=1; i <= 15; i++)
        {
            if (data[i].code == buttons_state)
            {
                if (use_long)
                {
                    usleep (200000);
                    while ((buttons_state = get_pin_status()) && (data[i].code == buttons_state))
                    {
                        run_command(i+15);
                        was_long = 1;
                        usleep (100000);
                    }
                }

                if (!was_long)
                {
                    run_command (i);
                    usleep (150000);
                }

                break;
            }
        }
        usleep (50000);
    }
}

void
dohelp()
{
    fprintf (stdout, "%s version %s. Usage:\n\n", procname, procversion);

    fprintf (stdout, "\t--help\t\t - this help\n");
    fprintf (stdout, "\t--kill\t\t - kill daemon\n");
    fprintf (stdout, "\t--killforce\t - kill daemon forced (Not recommended)\n");
    fprintf (stdout, "\t--nodaemon\t - don't use daemon. Start in foreground.\n");
    fprintf (stdout, "\t-f <file>\t - use alternate configuration file\n");
    fprintf (stdout, "\nPlease report any bugs to: the.same.denis@gmail.com\n");
}

int get_pid(bool remove_file)
{
    int i, pid;
    FILE *pidfile;

    for (i = 0; i < sizeof(pid_files) / sizeof(char *); i++)
    {
    if (pid_files[i] != NULL)
    {
        pidfile = fopen(pid_files[i], "r");
        if (pidfile != NULL)
        {
            if (fscanf(pidfile,"%d", &pid)) 
            {
                fclose (pidfile);
            if (remove_file)
            if (remove(pid_files[i]) == -1)
                fprintf(stderr, "%s: Error occured when deleting PID-file (Error %d: %s).\n", procname, errno, strerror(errno));
                return pid;
            } else {
                fclose (pidfile);
            }
        }
    }
    }
    return 0;
}

int write_pid(int pid)
{
    int i;
    FILE *pidfile;

    for (i = 0; i < sizeof(pid_files) / sizeof(char *); i++)
    {
    if (pid_files[i] != NULL)
    {
        pidfile = fopen(pid_files[i], "w");
        if (pidfile != NULL)
        {
            if (fprintf(pidfile, "%d", pid)) 
            {
                fclose (pidfile);
                return 1;
            } else {
                fclose (pidfile);
            }
        }
    }
    }
    return 0;
}

void
init_user_dependent_vars()
{
    char *buf;
    char *base;
    int i, l1, l2;

    struct passwd *pw = getpwuid(getuid());
    if (pw)
        base = pw->pw_dir;
    else
    {
        base = (char *)malloc(4);
        strcpy(base, "/tmp");
    }

    l1 = strlen(base);

    // config files
    for (i = 0; i < sizeof(config_files) / sizeof(char *); i++)
    {
    if (config_files[i] != NULL)
    {
        if (config_files[i][0] == '~')
        {
        l2 = strlen(config_files[i]);
        buf = (char *)malloc(l1 + l2 - 1);
        strcpy(buf, base);
        strcat(buf, config_files[i] + 1);
        config_files[i] = buf;
        }
    }
    }

    // pid-files
    for (i = 0; i < sizeof(pid_files) / sizeof(char *); i++)
    {
    if (pid_files[i] != NULL)
    {
        if (pid_files[i][0] == '~')
        {
        l2 = strlen(pid_files[i]);
        buf = (char *)malloc(l1 + l2 - 1);
        strcpy(buf, base);
        strcat(buf, pid_files[i] + 1);
        pid_files[i] = buf;
        }
    }
    }
}

int
get_settings()
{
    int i;

    init_user_dependent_vars();

    for (i = 0; i < sizeof(config_files) / sizeof(char *); i++)
    {
    if (config_files[i] != NULL)
    {
        if (!read_config(config_files[i]))
        fprintf(stderr, "%s: Reading configuration file '%s' failed. Trying %s.\n", procname, config_files[i], i != sizeof(config_files) / sizeof(char *) - 1 ? "next one" : "defaults");
        else
        {
        if (data[32].command != NULL)
        {
            fprintf(stderr, "DBG: custom_pid\n");
            pid_files[0] = data[32].command;
        }
        return 1;
        }
    }
    }
    return 0;
}

int
main (int argc, char** argv)
{
    int nodaemon = 0;
    int k;
    pid_t m_pid;

    procname = basename(argv[0]);

    if (argc != 0)
    {
        for (k = 1; k < argc; k++)
        {
            if (strcasecmp(argv[k], "--help") == 0)
            {
                dohelp();
                return 0;
            }

            if (strcasecmp(argv[k], "-f") == 0)
            {
                if (argc < k + 2)
                {
                    fprintf(stderr, "%s: Please specify a configuration file name!\n", procname);
                    return 1;
                }

                config_files[0] = (char *)malloc (sizeof(char) * (strlen(argv[k+1])+1));
                strcpy (config_files[0], argv[k+1]);
                k++;
                continue;
            }

            if (strcasecmp(argv[k], "--kill") == 0)
            {
                get_settings();

        int pid = get_pid(true);

                if (pid)
                {
                    kill (pid, SIGTERM);
                    return 0;
                }

                fprintf (stderr, "%s: Error reading PID-file. Is %s running?\n", procname, procname);
                return 1;
            }

            if (strcasecmp(argv[k], "--killforce") == 0)
            {
                char *cmd = (char *)malloc (sizeof(char) * (strlen(procname) + 8 + 1));
                strcpy (cmd, "killall ");
                strcat (cmd, procname);
                execlp ("sh", "sh", "-c", cmd, (char *) NULL);
                return 0;
            }

            if (strcasecmp(argv[k], "--nodaemon") == 0)
            {
                nodaemon = 1;
                continue;
            }

            fprintf(stderr, "%s: Unknown option: '%s'. Run '%s --help' for usage information.\n", procname, argv[k], procname);
            return 1;
        }
    }

    get_settings();
    if (!nodaemon)
    {
        int pid = get_pid(false);

        if (pid)
        {
            fprintf(stderr, "%s: Another copy of %s with PID %d is running!.\n", procname, procname, pid);
            return 1;
        }

        m_pid = fork();
        switch (m_pid)
        {
            case 0: // Child
                break;
            case -1: // Error
                fprintf (stderr, "%s: Error in fork()\n", procname);
                return 1;
            default:
                if (!write_pid (m_pid))
                {
                    fprintf(stderr, "%s: warning: Couldn't save PID-file. Ignoring.\n", procname);
                }
                return 0;
        }

    }

    pd = open(data[31].command, O_RDONLY);
    if (pd == -1)
    {
        fprintf (stderr, "%s: Port opening error. Check your settings and permissions.\n", procname);
        get_pid(true); /* remove PID-file */
        return 1;
    }
    if (strcasecmp(data[33].command, "yes") == 0)
            use_long = 1;

    check_buttons();
    return 0;
}

